/*
 *  List learning With Hash studying
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include "util.h"
 
struct clown {
	unsigned long long shoe_size;
	const char * name;
	struct list_head node;
};


static struct clown* clown_create(unsigned long long shoe_size, const char * name){
	struct clown * c = kmalloc(sizeof(struct clown), GFP_KERNEL);
	if(c){
		c->shoe_size = shoe_size;
		c->name = name;
		INIT_LIST_HEAD(&c->node);
		return c;
	}
	return NULL;
}
static void clown_destory(struct clown * clown){
	if(clown){
		kfree(clown);
	}
}




struct clown_car {
	int tyre_pressure[4];
	struct list_head clowns;
};


static inline void clown_car_deinit(struct clown_car * car){
	if(car){
		//memset(car->tyre_pressure, 0, sizeof(car->tyre_pressure));	
		//INIT_LIST_HEAD(&car->clowns);
	}
}
static int clown_car_init(struct clown_car * car){
	if(car){
		car->tyre_pressure[0] = 10;
		car->tyre_pressure[1] = 12;
		car->tyre_pressure[2] = 11;
		car->tyre_pressure[3] = 9;

		INIT_LIST_HEAD(&car->clowns);
		return 0;
	}
	return -EINVAL;
}

// add Grock, Dimitri, Pic, Alfredo
static struct clown * clown_car_add_nodes_ret_pic(struct clown_car * car){
  struct clown * c = clown_create(10, "Grock");
  list_add(&c->node, &car->clowns);

  c = clown_create(11, "Dimitri");
  list_add_tail(&c->node, &car->clowns);

  struct clown * pic = clown_create(12, "Pic");
  list_add_tail(&pic->node, &car->clowns);

  c = clown_create(13, "Alfredo");
  list_add_tail(&c->node, &car->clowns);

  return pic;
}

static void clown_car_visit(struct list_head * list, const char * msg)
{
  pr_alert("visit each node: [%s]:", msg);
  struct list_head * cur;
  list_for_each(cur, list){
    struct clown * e = list_entry(cur, struct clown, node);
    pr_alert("\t-> name:[%s], shoe_size:[%llu]\n", e->name, e->shoe_size);
  }
}
static void clown_car_del_all_nodes(struct list_head * list)
{
  struct clown * cur;
  struct clown * n;
  list_for_each_entry_safe(cur, n, list, node){
    list_del_init(&cur->node);
    //list_del(&cur->node);
    clown_destory(cur);
  }
}

static void test_static_clown_car_init(void){
	struct clown_car car = {
		.tyre_pressure = {10, 12, 11, 9},
		.clowns = LIST_HEAD_INIT(car.clowns)
	};

	INIT_LIST_HEAD(&car.clowns);
}

static int clown_car_add_visit_del(struct clown_car * car){
	if(!car)
    return -EINVAL;
  clown_car_add_nodes_ret_pic(car);

  //visit  with list_for_each
  {
    struct list_head * cur;
    list_for_each(cur, &car->clowns){
      struct clown * e = list_entry(cur, struct clown, node);
      pr_alert("list_for_each -> name:[%s], shoe_size:[%llu]\n", e->name, e->shoe_size);
    }
  }
  //visit  with list_for_each_entry
  clown_car_visit(&car->clowns, "clowns");

  //del visit with list_..._entry_safe
  clown_car_del_all_nodes(&car->clowns);
  return 0;
}

static void test_list_add_visit_del(void){
	struct clown_car car;
	int ret = clown_car_init(&car);
	if(!ret){
		ret = clown_car_add_visit_del(&car);
		if(ret){
			pr_err("add car & visit & del car failed\n");
		}
		clown_car_deinit(&car);
	}
	else{
		pr_err("clown car init failed\n");
	}
}



static int clown_car_add_cut_del(struct clown_car * car){
  if(!car) 
    return -EINVAL;

  // add Grock, Dimitri, Pic, Alfredo
  struct clown * pic = clown_car_add_nodes_ret_pic(car);

  // visit for it
  clown_car_visit(&car->clowns, "clowns");
   
  //cut clowns  into  [clowns: Alfredo]  [retirement: Grock, Dimitri, Pic]
  struct list_head retirement = LIST_HEAD_INIT(retirement);
  list_cut_position(&retirement, &car->clowns, &pic->node);
  clown_car_visit(&car->clowns, "clowns has been cut -> clowns");
  clown_car_visit(&retirement, "clowns has been cut -> retirement");


  //splice [clowns: Grock, Dimitri, Pic, Alfredo] [retirement: ]
  list_splice_init(&retirement, &car->clowns);
  clown_car_visit(&car->clowns, "splice retirement to clowns -> clowns");
  clown_car_visit(&retirement, "splice retirement to clowns -> retirement");

  //cut clowns before pic: [clowns: Pic, Alfredo]  [retirement: Grock, Dimitri]
  pr_alert("cut clowns before pic");
  list_cut_before(&retirement, &car->clowns, &pic->node);
  clown_car_visit(&car->clowns, "clowns has been cut_before -> clowns");
  clown_car_visit(&retirement, "clowns has been cut_before -> retirement");

  //splice [clowns: Grock, Dimitri, Pic, Alfredo] [retirement: ]
  list_splice_init(&retirement, &car->clowns);
  clown_car_visit(&car->clowns, "splice retirement to clowns -> clowns");
  clown_car_visit(&retirement, "splice retirement to clowns -> retirement");

  // destroy nodes (clowns & retirement)
  clown_car_del_all_nodes(&retirement);
  clown_car_del_all_nodes(&car->clowns);
  return 0;
}



static void test_list_add_cut_del(void){
  // create head (clowns)
  struct clown_car car;
  int ret = clown_car_init(&car);
  if(!ret){
    ret = clown_car_add_cut_del(&car);
    if(ret){
      pr_err("add & cut & del car failed\n");
    }
    clown_car_deinit(&car);
  }
}


static int clown_car_add_move_del(struct clown_car * car){
  if(!car) 
    return -EINVAL;

  // add [clowns: Grock, Dimitri, Pic, Alfredo]
  struct clown * pic = clown_car_add_nodes_ret_pic(car);
  struct clown * dimitri = list_prev_entry(pic, node);


  // create [sidewalk: Pio]
  struct list_head sidewalk = LIST_HEAD_INIT(sidewalk);
  struct clown * c = clown_create(14, "Pio");
  if(c)
    list_add(&c->node, &sidewalk);

  clown_car_visit(&car->clowns, "clowns");
  clown_car_visit(&sidewalk, "sidewalk");

  //move  [clowns: Grock, Dimitri, Alfredo] [sidewalk: Pic, Pio, Dimitri]
  list_move(&pic->node, &sidewalk); 
  list_move_tail(&dimitri->node, &sidewalk); // move pic to tail of sidewalk
  clown_car_visit(&car->clowns, "moved clowns");
  clown_car_visit(&sidewalk, "moved sidewalk");

  //move subset of list to tail
  struct clown * pio = list_next_entry(pic, node);
  list_bulk_move_tail(&sidewalk, &pic->node, &pio->node);
  clown_car_visit(&car->clowns, "bulk moved clowns");
  clown_car_visit(&sidewalk, "bulk moved sidewalk");

  clown_car_del_all_nodes(&sidewalk);
  clown_car_del_all_nodes(&car->clowns);
  return 0;
}


static void  test_list_add_move_del(void) {
  struct clown_car car;
  int ret = clown_car_init(&car);
  if(!ret) {
    ret = clown_car_add_move_del(&car);
    if(ret){
      pr_err("add & move & del car failed\n");
    }
    clown_car_deinit(&car);
  }
}


static int clown_car_add_swap_del(struct clown_car * car){
  if(!car) 
    return -EINVAL;

  // add [clowns: Grock, Dimitri, Pic, Alfredo]
  struct clown * pic = clown_car_add_nodes_ret_pic(car);
  struct clown * dimitri = list_prev_entry(pic, node);
  
  clown_car_visit(&car->clowns, "clowns");

  list_swap(&pic->node, &dimitri->node);
  clown_car_visit(&car->clowns, "swap pic & dimitri -> clowns");

  
  clown_car_del_all_nodes(&car->clowns);

  return 0;
}

static void test_list_add_swap_del(void) {
  struct clown_car car;
  int ret = clown_car_init(&car);
  if(!ret) {
    ret = clown_car_add_swap_del(&car);
    if(ret){
      pr_err("add & swap & del car failed\n");
    }
    clown_car_deinit(&car);
  }
}


static int clown_car_add_rotate_del(struct clown_car * car){
  if(!car)
    return -EINVAL;

  // add [clowns: (Grock), Dimitri, Pic, Alfredo]
  clown_car_add_nodes_ret_pic(car);
  clown_car_visit(&car->clowns, "clowns");

  //rotate_left (first -> last)  [clowns: Dimitri, Pic, Alfredo, (Grock)]
  list_rotate_left(&car->clowns);
  clown_car_visit(&car->clowns, "rotate left for clowns");

  clown_car_del_all_nodes(&car->clowns);
  return 0;
}

static void test_list_add_rotate_del(void){

  struct clown_car car;
  int ret = clown_car_init(&car);
  if(!ret) {
    ret = clown_car_add_rotate_del(&car);
    if(ret){
      pr_err("add & rotate & del car failed\n");
    }
    clown_car_deinit(&car);
  }
}

static void tc_list(void){
	pr_alert("this is test case for list");

	test_static_clown_car_init(); //tc1 - static init list_head
													 //
	test_list_add_visit_del();

  test_list_add_cut_del();

  test_list_add_move_del();
  test_list_add_swap_del();
  test_list_add_rotate_del();
}


#define   BLK_DEV_FILE    BIT(0)
#define   BLK_DEV_HDD     BIT(1)

static void tc_flag(void){
  unsigned int flag = 0x0;
  if(!(flag & (BLK_DEV_FILE | BLK_DEV_HDD))){// is not file and not hdd
    pr_alert("not file and not hdd->0x%x (0x1-file, 0x2-hdd)\n", flag);
  }

  //set bit to flag
  flag |= BLK_DEV_FILE;

  if(flag & BLK_DEV_FILE) { // is block_dev_file
    pr_alert("is file->0x%x (0x1-file, 0x2-hdd)\n", flag);
  }
  if(!(flag & BLK_DEV_HDD)){ // is not block_dev_hdd
    pr_alert("not hdd->0x%x (0x1-file, 0x2-hdd)\n", flag);
  }

  if(flag & (BLK_DEV_HDD | BLK_DEV_FILE)){ // is block_dev_file or block_dev_hdd
    pr_alert("is file or hdd->0x%x (0x1-file, 0x2-hdd)\n", flag);
  }

  if(!(flag & (BLK_DEV_FILE | BLK_DEV_HDD))){// not file and not hdd
    pr_alert("not file and not hdd->0x%x (0x1-file, 0x2-hdd)\n", flag);
  }


  flag &= ~BLK_DEV_FILE;
  if(flag & BLK_DEV_FILE) {
    pr_alert("is block device of file, 0x%x, 0x1-file, 0x2-hdd\n", flag);
  }
  else{
    pr_alert("not block device of file, 0x%x, 0x1-file, 0x2-hdd\n", flag);
  }

  flag |= (BLK_DEV_FILE|BLK_DEV_HDD);
  if((flag & (BLK_DEV_HDD | BLK_DEV_FILE)) == (BLK_DEV_FILE | BLK_DEV_HDD) ){ // is block_dev_file and block_dev_hdd
    pr_alert("is file and hdd->0x%x (0x1-file, 0x2-hdd)\n", flag);
  }
}


 
static int __init hello_init(void) {
  printk(KERN_ALERT "\nHello, world!\n");


  tc_list();

  tc_flag();
  return 0;
}
 
static void __exit hello_exit(void) {
    printk(KERN_ALERT "Goodbye, cruel world!\n\n");
}
 
module_init(hello_init);
module_exit(hello_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Cheng Lun");
MODULE_DESCRIPTION("A Simple Hello World Module");
MODULE_VERSION("0.01");
